﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using HK.Core.Common.Data;
using HK.Core.Encryption.Protocol;
using HK.Core.IO.Protocol.Output;
using HLogger = HK.Core.Logs.Logger;

namespace HK.Core.Utils
{
    
    /// <summary>
    /// 未知加密选项异常
    /// </summary>
    /// <typeparam name="TData">数据类型</typeparam>
    public class UnknownEncryptOptionException<TData> : NotImplementedException
        where TData : JsonData, IBinaryEncrypt
    {
        public UnknownEncryptOptionException(EncryptOption iOption) : 
            base($"Unknown Encrypt Option!(Type:{typeof(TData).Name} Option:{iOption})") { }
        
        public UnknownEncryptOptionException(EncryptOption iOption, string iFilePath) : 
            base($"Unknown Encrypt Option!(Type:{typeof(TData).Name} Option:{iOption} File:{iFilePath})") { }
    }
    
    /// <summary>
    /// 工具类 : 加密解密.
    /// </summary>
    public static class UtilsEncrypts
    {
        /// <summary>
        /// 默认密钥(默认)
        /// </summary>
        private static readonly string DefaultSecretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
        
        /// <summary>
        /// 加密数据
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iData">数据</param>
        /// <param name="iOption">加密选项</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <returns>true:OK; false:NG;</returns>
        /// <exception cref="UnknownEncryptOptionException{TData}">未知加密类型</exception>
        public static bool Encode<TData>(string iFilePath, TData iData, 
            EncryptOption iOption, Encoding iEncoding, string iSecretKey = null)
            where TData : JsonData, IBinaryEncrypt
        {
            if (string.IsNullOrEmpty(iFilePath) || default(TData) == iData || !iData.Valid) return false;
            
            // 密钥
            var secretKey = string.IsNullOrEmpty(iSecretKey) ? DefaultSecretKey : iSecretKey;
            if (string.IsNullOrEmpty(secretKey)) return false;
            var secretKeyBytesTmp = iEncoding.GetBytes(secretKey);
            
            var flg = false;

#if UNITY_EDITOR
            try
            {
#endif
                string context = null;
                switch (iOption)
                {
                    case EncryptOption.Aes:
                    {
                        var secretKeyBytes = new byte[16];
                        Array.Copy(secretKeyBytesTmp, secretKeyBytes, secretKeyBytes.Length);
                        context = EncodeAes<TData>(iData, iEncoding, secretKeyBytes, secretKeyBytes);

                    }
                        break;
                    case EncryptOption.Des:
                    {
                        var secretKeyBytes = new byte[8];
                        Array.Copy(secretKeyBytesTmp, secretKeyBytes, secretKeyBytes.Length);
                        context = EncodeDes<TData>(iData, iEncoding, secretKeyBytes, secretKeyBytes);
                    }
                        break;
                    default:
                        throw new UnknownEncryptOptionException<TData>(iOption, iFilePath);
                }
                
                // 写入文件
                if (!string.IsNullOrEmpty(context))
                {
                    flg = UtilsIo.WriteTxtFile(iFilePath, context, true);
                }
                
#if UNITY_EDITOR
            }
            catch (Exception iExp)
            {
                HLogger.Fatal($"UtilsEncrypts::Encode():Type:{typeof(TData).Name} " +
                              $"\n  File:{iFilePath}" +
                              $"\n  Exception:{iExp.GetType().Name} Exception:{iExp.GetType().Name}" +
                              $"\n  Error:{iExp.Message} Exception:{iExp.GetType().Name}" +
                              $"\n  StackTrace:{iExp.StackTrace}");
            }
#endif
            
            return flg;
        }

        /// <summary>
        /// 解密数据
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iOption">加密选项</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <returns>true:OK; false:NG;</returns>
        public static TData Decode<TData>(string iFilePath, EncryptOption iOption,
            Encoding iEncoding, string iSecretKey = null)
            where TData : JsonData, IBinaryEncrypt
        {
            if (string.IsNullOrEmpty(iFilePath)) return default(TData);

            // 文件若存在，则删除
            if (!File.Exists(iFilePath))
            {
                HLogger.Error($"Decode():File is not exist!(Path:{iFilePath})");
                return default(TData);
            }

            var encodeContext = File.ReadAllText(iFilePath);
            if (string.IsNullOrEmpty(encodeContext))
            {
                return default(TData);
            }
            
            var secretKey = string.IsNullOrEmpty(iSecretKey) ? DefaultSecretKey : iSecretKey;
            if (string.IsNullOrEmpty(secretKey)) return default(TData);
            var secretKeyBytesTmp = iEncoding.GetBytes(secretKey);
            
            var targetData = default(TData);

#if UNITY_EDITOR
            try
            {
#endif
                switch (iOption)
                {                    
                    case EncryptOption.Aes:
                    {
                        var secretKeyBytes = new byte[16];
                        Array.Copy(secretKeyBytesTmp, secretKeyBytes, secretKeyBytes.Length);
                        targetData = DecodeAes<TData>(encodeContext, iEncoding, secretKeyBytes, secretKeyBytes);
                    }
                        break;
                    case EncryptOption.Des:
                    {
                        
                        var secretKeyBytes = new byte[8];
                        Array.Copy(secretKeyBytesTmp, secretKeyBytes, secretKeyBytes.Length);
                        targetData = DecodeDes<TData>(encodeContext, iEncoding, secretKeyBytes, secretKeyBytes);
                    }
                        break;
                    default:
                        throw new UnknownEncryptOptionException<TData>(iOption, iFilePath);
                }
                
#if UNITY_EDITOR
            }
            catch (Exception iExp)
            {
                HLogger.Fatal($"UtilsEncrypts::Decode():Type:{typeof(TData).Name} " +
                              $"\n  File:{iFilePath}" +
                              $"\n  Exception:{iExp.GetType().Name} Exception:{iExp.GetType().Name}" +
                              $"\n  Error:{iExp.Message} Exception:{iExp.GetType().Name}" +
                              $"\n  StackTrace:{iExp.StackTrace}");
            }
#endif

            return targetData;
        }

#region Aes

        /// <summary>
        /// 加密数据(Aes)
        /// </summary>
        /// <param name="iData">数据内容</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iIv">加密用初始化向量</param>
        /// <returns>true:OK; false:NG;</returns>
        private static string EncodeAes<TData>(TData iData, Encoding iEncoding, byte[] iSecretKey, byte[] iIv)
            where TData : JsonData, IBinaryEncrypt
        {

            if (default(TData) == iData || !iData.Valid) return null;

            var context = UtilsJson<TData>.ConvertToJsonString(iData, false);
            if (string.IsNullOrEmpty(context)) return null;

            string encodeStr;
            using (var aes = new AesCryptoServiceProvider())
            {
                aes.Key = iSecretKey;
                aes.IV = iIv;

                var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
                byte[] encryptedBytes = null;
                using (var ms = new MemoryStream())
                {
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        var contextBytes = iEncoding.GetBytes(context);
                        cs.Write(contextBytes, 0 , contextBytes.Length);
                    }
                    encryptedBytes = ms.ToArray();
                }
                encodeStr = UtilsBase64.Base64Encode(encryptedBytes);
            }

            return encodeStr;
        }
        
        /// <summary>
        /// 解密数据(Aes)
        /// </summary>
        /// <param name="iEncodeContext">加密过的内容</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iIv">加密用初始化向量</param>
        /// <returns>解密数据</returns>
        private static TData DecodeAes<TData>(string iEncodeContext, Encoding iEncoding, byte[] iSecretKey, byte[] iIv)
            where TData : JsonData, IBinaryEncrypt
        {

            if (string.IsNullOrEmpty(iEncodeContext)) return default(TData);

            byte[] decryptedBytes = null;
            using (var aes = new AesCryptoServiceProvider())
            {
                aes.Key = iSecretKey;
                aes.IV = iIv;

                var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
                var encryptedBytes = UtilsBase64.Base64Decode(iEncodeContext);
                using (var ms = new MemoryStream(encryptedBytes))
                {
                    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        var buffer = new byte[encryptedBytes.Length];
                        var readLen = cs.Read(buffer, 0, buffer.Length);
                        decryptedBytes = new byte[readLen];
                    
                        Array.Copy(buffer, decryptedBytes, readLen);
                    }   
                }
            }

            if (0 < decryptedBytes.Length)
            {
                var decodeContext = iEncoding.GetString(decryptedBytes);
                if (string.IsNullOrEmpty(decodeContext)) return default(TData);

                return UtilsJson<TData>.ConvertFromJsonString(decodeContext);
            }
            
            return default(TData);
        }

#endregion

#region Des
        
        /// <summary>
        /// 加密数据(Des)
        /// </summary>
        /// <param name="iData">数据内容</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iIv">加密用初始化向量</param>
        /// <returns>true:OK; false:NG;</returns>
        private static string EncodeDes<TData>(TData iData, Encoding iEncoding, byte[] iSecretKey, byte[] iIv)
            where TData : JsonData, IBinaryEncrypt
        {

            if (default(TData) == iData || !iData.Valid ||
                null == iSecretKey || 0 >= iSecretKey.Length ||
                null == iIv || 0 >= iIv.Length) return null;

            var context = UtilsJson<TData>.ConvertToJsonString(iData, false);
            if (string.IsNullOrEmpty(context)) return null;

            string encodeStr;
            using (var des = new DESCryptoServiceProvider())
            {
                des.Key = iSecretKey;
                des.IV = iIv;

                var encryptor = des.CreateEncryptor(des.Key, des.IV);
                byte[] encryptedBytes = null;
                using (var ms = new MemoryStream())
                {
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        var contextBytes = iEncoding.GetBytes(context);
                        cs.Write(contextBytes, 0 , contextBytes.Length);
                    }
                    encryptedBytes = ms.ToArray();
                }
                encodeStr = UtilsBase64.Base64Encode(encryptedBytes);
            }

            return encodeStr;
        }
        
        /// <summary>
        /// 解密数据(Des)
        /// </summary>
        /// <param name="iEncodeContext">加密过的内容</param>
        /// <param name="iEncoding">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iIv">加密用初始化向量</param>
        /// <returns>解密数据</returns>
        private static TData DecodeDes<TData>(string iEncodeContext, Encoding iEncoding, byte[] iSecretKey, byte[] iIv)
            where TData : JsonData, IBinaryEncrypt
        {

            if (string.IsNullOrEmpty(iEncodeContext)) return default(TData);

            byte[] decryptedBytes = null;
            using (var des = new DESCryptoServiceProvider())
            {
                des.Key = iSecretKey;
                des.IV = iIv;

                var decryptor = des.CreateDecryptor(des.Key, des.IV);
                var encryptedBytes = UtilsBase64.Base64Decode(iEncodeContext);
                using (var ms = new MemoryStream(encryptedBytes))
                {
                    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        var buffer = new byte[encryptedBytes.Length];
                        var readLen = cs.Read(buffer, 0, buffer.Length);
                        decryptedBytes = new byte[readLen];
                    
                        Array.Copy(buffer, decryptedBytes, readLen);
                    }   
                }
            }

            if (0 < decryptedBytes.Length)
            {
                var decodeContext = iEncoding.GetString(decryptedBytes);
                if (string.IsNullOrEmpty(decodeContext)) return default(TData);

                return UtilsJson<TData>.ConvertFromJsonString(decodeContext);
            }
            
            return default(TData);
        }
        
#endregion
        
    }
}
